 #pragma once

#include "SenetBoard.h"
#include "SenetBoardDraftMove.h"
#include <Vector>

//Senet board which we can use to retrieve the information we need on the board
class SenetBoardDraft
{
	
public:
	//C'tor
	SenetBoardDraft(const SenetBoard& board, int boardSize, int initialPiecesNum);
	
	//D'tor
	~SenetBoardDraft();

	const SenetBoard::SquareData& operator[] (const int a) const;
	
	//generate available moves for player with given stickNum
	//returns:
	//  return value - number of available moves
	//  moves - will hold the vector of available moves
	void generateMoves(vector<int>& moves, Definitions::PlayerColor player , int stickNum) const;
	
	//perform move on board.
	//does not check for move legality (efficiency)
	//returns the actual final position of the piece (possibly different the move_to_square because of rebirth)
	void doMove(vector<SenetBoardDraftMove>& movesPerformed, int pieceToMove, int move_to_square , 
					Definitions::PlayerColor currentPlayer);

	void undoMove(int pieceToMove , int move_to_square , Definitions::PlayerColor);

	//return a vector containing the pieces of playerColor
	//vector size == number of pieces of playerColor
	//this function return a copy (and therefore is more expansive the regular getPieceLocations)
	inline void getPiecesLocations(
		Definitions::PlayerColor playerColor,
		vector<int>& outputVector) const
	{
		PiecesLocIndex player = getPiecesLocIndexFromPlayer(playerColor);
		outputVector.resize(m_playerPieceNum[player]);
		for(int i = 0; i < m_playerPieceNum[player] ; ++i)
		{
			outputVector[i] = m_piecesLoc[player][i];
		}

	}

	inline bool isSafePosition(int position) const
	{
		return (m_board[position].square_type == SenetBoard::SAFE);
	}

	inline const vector<int>& getPiecesLocations(Definitions::PlayerColor playerColor) const
	{
		if (playerColor == Definitions::WHITE)
		{
			return whitePiecesLocations();
		}
		else
		{
			return blackPiecesLocations();
		}

	}

	inline int hashCode()
	{
		int key = 0;
		int mul = 2;
		for (int i = 0; i < m_boardSize; ++i)
		{
			key += i * ((int)this->m_board[i].player_color + 1 + i) * mul;
			mul *= 2;
		}
		return hash32shift(key);
	}

	static inline int hash32shift(int key)
	{
		key = ~key + (key << 15); // key = (key << 15) - key - 1;
		key = key ^ (key >> 12);
		key = key + (key << 2);
		key = key ^ (key >> 4);
		key = key * 2057; // key = (key + (key << 3)) + (key << 11);
		key = key ^ (key >> 16);
		return key;
	}


	//location of white pieces
	inline const vector<int>& whitePiecesLocations() const 
		{return m_piecesLoc[WHITE];}

	//number of white pieces
	inline int whitePiecesNum() const {return m_playerPieceNum[WHITE];}
	
	//location of black pieces
	inline const vector<int>& blackPiecesLocations() const
		{return m_piecesLoc[BLACK];}

	
	//number of black pieces
	inline int blackPiecesNum() const {return m_playerPieceNum[BLACK];}

	
	//is terminal board
	inline bool isTerminal() const {return ((m_playerPieceNum[WHITE] == 0) || (m_playerPieceNum[BLACK] == 0));} 



	
	//number of safe squares
	inline int numSafeSquares() const
	{
		return m_safeSquareNum;
	}

	//number of board squares
	inline int getBoardSize() const
	{
		return m_boardSize;
	}
	
	//TODO: DEBUG - remove following public functions before submission
	/*inline void setRebirth(int square , int rebirthTo)
	{
		m_board[square].square_type = SenetBoard::REBIRTH_FROM;
		m_board[square].rebirth_to = rebirthTo;
	}

	inline void SetSafe(int square)
	{
		m_board[square].square_type = SenetBoard::SAFE;
	}

	
	inline bool isLegal(int pieceToMove, Definitions::PlayerColor player , int stickNum) const
	{
		return isLegalMove(pieceToMove, player, stickNum);
	}
	*/
	/*bool operator==(const SenetBoardDraft& other) const 
	{
		for(int i = 0; i < m_boardSize; i++)
		{
			if(m_board[i].player_color != other.m_board[i].player_color)
			{
				return false;
			}
		}
		return true;
	}*/

	/*SenetBoardDraft(const SenetBoardDraft& other)
	{
		m_board.resize(other.m_boardSize);
		m_board = other.m_board;
		m_boardSize = other.m_boardSize;
	}*/


private:
	//Fields:
	SenetBoard::BoardState m_board;
	
	vector<vector<int>> m_piecesLoc;		//location of pieces.
									//WHITE (0) - white pieces
									//BLACK (1) - black pieces
	int m_piecesOnFirstThird[2];	//number of pieces on first third
									//WHITE (0) - white pieces
									//BLACK (1) - black pieces
	int m_playerPieceNum[2];		//number of pieces remaining on board
									//WHITE (0) - white pieces
									//BLACK (1) - black pieces
	int m_boardSize;				//board size
	int m_boardThirdSize;			//board third size (== boardsize / 3)
	int m_safeSquareNum;			//number of safe squares

	//Constants:
	static const int m_bigHouseSize = 3;	//size of big house
	static const int m_smallHouseSize = 2;	//size of small house
	typedef enum {WHITE , BLACK} PiecesLocIndex;	//index of the pieces location array in m_piecesLoc

	//Methods:
	void extractBoardData(const SenetBoard& board, int boardSize,  int initialPiecesNum);

	//inline:
	inline PiecesLocIndex getPiecesLocIndexFromPlayer(Definitions::PlayerColor player) const
	{
		if(player == Definitions::WHITE)
		{
			return WHITE;
		}
		if(player == Definitions::BLACK)
		{
			return BLACK;
		}
		
		throw "Critical Error - SenetBoardDraft.getPiecesLocIndexFromPlayer: player color is undefined";
	}
	inline Definitions::PlayerColor getPlayerFromPieceLocIndex(PiecesLocIndex player) const
	{
		if(player == WHITE)
		{
			return Definitions::WHITE;
		}
		else
		{
			return Definitions::BLACK;
		}
	}
	inline Definitions::PlayerColor getOppositeColor(Definitions::PlayerColor color) const
	{
		if(color == Definitions::WHITE)
		{
			return Definitions::BLACK;
		}
		if(color == Definitions::BLACK)
		{
			return Definitions::WHITE;
		}
		return Definitions::NONE;
	}

	inline PiecesLocIndex getOpositeLocIndex(PiecesLocIndex player) const
	{
		if(player == WHITE)
		{
			return BLACK;
		}
		return WHITE;
	}

	inline void updateLocation(PiecesLocIndex player, int origPosition, int finalPosition)
	{
		//update locations vector
		for(int i = 0 ; i < m_playerPieceNum[player] ; ++i)
		{
			if(m_piecesLoc[player][i] == origPosition)
			{
				m_piecesLoc[player][i] = finalPosition;
				break;
			}	
		}
		
		//update boardThird counter
		if((finalPosition >= m_boardThirdSize) && (origPosition < m_boardThirdSize))
		{
			//player moved after first board third
			--(m_piecesOnFirstThird[player]);
		}
		else if((finalPosition < m_boardThirdSize) && (origPosition >= m_boardThirdSize))
		{
			//player moved inside first board third (possible in case of eat)
			++(m_piecesOnFirstThird[player]);
		}

	}
	inline int playerMaxSequence(int startLoc, int endLoc, Definitions::PlayerColor player) const
	{
		int maxSequence = 0;
		int currentSequence = 0;
		for(int i = startLoc ; i <= endLoc; ++i)
		{
			if(i == m_boardSize)
			{
				break;
			}
			if(m_board[i].player_color == player)
			{
				++currentSequence;
			}
			else
			{
				currentSequence = 0;
			}
			if(currentSequence > maxSequence)
			{
				maxSequence = currentSequence;
			}
			
		}
		return maxSequence;
	}

	bool isLegalMove(int pieceToMove, Definitions::PlayerColor currentPlayer , int stickNum) const
	{	
		//TODO: Debug checkBoardCoherancy();
		if(m_board[pieceToMove].player_color != currentPlayer)
		{
			return false; //player must have a piece to move in the requested square
		}

		if(stickNum == 0)
		{
			//TODO: change to false and see what happen (need to return -1 for empty move)
			return true; //empty move is always legal
		}

		int finalLoc = pieceToMove + stickNum;

		Definitions::PlayerColor oppositeColor = getOppositeColor(currentPlayer);

		// cannot jump over blocking sequence
		if(playerMaxSequence(pieceToMove , finalLoc , oppositeColor) >= m_bigHouseSize)
		{
			return false;
		}
		
		if(finalLoc >= m_boardSize)
		{
			//move will remove piece from the board


			//can't remove piece if have a piece in first third of board
			if(m_piecesOnFirstThird[getPiecesLocIndexFromPlayer(currentPlayer)])
			{
				return false;
			}
			
		}
		else
		{
			//move will not remove the piece from the board

			Definitions::PlayerColor finalLocColor = m_board[finalLoc].player_color;
			

			//can't land on a house
			if(finalLocColor == oppositeColor && 
				(m_board[finalLoc -1].player_color == oppositeColor || 
				(((finalLoc + 1) < m_boardSize) && (m_board[finalLoc + 1].player_color == oppositeColor))))
			{
				return false;
			}
			
			//can't eat opponent in a safe square
			if(finalLocColor == oppositeColor && m_board[finalLoc].square_type == SenetBoard::SAFE)
			{
				return false;
			}
			
			//can't land on a square with own piece
			if(finalLocColor == currentPlayer)
			{
				return false;
			}
		}
	
		return true;
	}


	inline void removePieceFromBoard(int pieceToMove, PiecesLocIndex player)
	{
		//remove piece from board
		m_board[pieceToMove].player_color = Definitions::NONE;
		
		//update pieces locations
		int locationToFill = -1;
		int lastUsedIndex = m_playerPieceNum[player]-1;
		for(int i = 0; i < m_playerPieceNum[player] ; ++i)
		{
			if(m_piecesLoc[player][i] == pieceToMove)
			{
				locationToFill = i;
				m_piecesLoc[player][i] = -1;
				break;
			}
			if(m_piecesLoc[player][i] == -1)
			{
				//this index is not used save the index before
				lastUsedIndex = m_piecesLoc[player][i] - 1;
				break;
			}
		}
		std::swap(m_piecesLoc[player][locationToFill],m_piecesLoc[player][lastUsedIndex]);
		
		//update player pieces number
		--m_playerPieceNum[player];		
		
		if(pieceToMove < m_boardThirdSize)
		{
			--(m_piecesOnFirstThird[player]);
		}
	}

	inline void returnPieceToBard(int squareToReturn , PiecesLocIndex player, Definitions::PlayerColor playerColor)
	{
		//return piece to board, no need to check for opponent
		m_board[squareToReturn].player_color = playerColor;

		//update locations
		m_piecesLoc[player][m_playerPieceNum[player]] = squareToReturn;
		
		//update player piece number
		++(m_playerPieceNum[player]);

		//update pieces on first third
		if(squareToReturn < m_boardThirdSize)
		{
			++m_piecesOnFirstThird[player];
		}
	}
	
	inline int findAvailableRebirthToSquare(int square) const
	{
		for(int i(m_board[square].rebirth_to); i >= 0; --i)
		{
			if(m_board[i].player_color == Definitions::NONE)
			{
				return i;
			}
		}
		return square;
	}
	
	inline void movePiece(vector<SenetBoardDraftMove>& movesPerformed ,int pieceToMove, int move_to_square , 
							PiecesLocIndex player , Definitions::PlayerColor playerColor)
	{
		int finalPieceLocation = move_to_square;

		if(m_board[move_to_square].player_color == getOppositeColor(playerColor))
		{
			//eat will be performed - update location of opposite player eaten piece
			updateLocation(getOpositeLocIndex(player), move_to_square , pieceToMove);
		}
		
		//do move:
		std::swap(m_board[pieceToMove].player_color, m_board[move_to_square].player_color);
		movesPerformed.push_back(SenetBoardDraftMove(pieceToMove, move_to_square));
		
		//check if the move was to a rebirth square (only possible for regular move, not for undo move).
		if(m_board[move_to_square].square_type == SenetBoard::REBIRTH_FROM)
		{
			finalPieceLocation = findAvailableRebirthToSquare(move_to_square);
			std::swap(m_board[move_to_square].player_color,
					  m_board[finalPieceLocation].player_color);
			movesPerformed.push_back(SenetBoardDraftMove(move_to_square , finalPieceLocation));
		}

		//update piece location:
		updateLocation(player, pieceToMove , finalPieceLocation);

	}

	//TODO: DEBUG: remove this function before submission
	////void checkBoardCoherancy () const
	//{
	//	//check white coherancy
	//	for(int i = 0 ; i < m_playerPieceNum[WHITE] ; i++)
	//	{
	//		if(m_board[m_piecesLoc[WHITE][i]].player_color != Definitions::WHITE)
	//		{
	//			throw "found Incoherancy!!!";
	//		}
	//	}
	//	//check black coherancy
	//	for (int i=0; i < m_playerPieceNum[BLACK] ; i++)
	//	{
	//		if(m_board[m_piecesLoc[BLACK][i]].player_color != Definitions::BLACK)
	//		{
	//			throw "found Incoherancy!!!";
	//		}
	//	}

	//	if(m_piecesLoc[WHITE][0] == m_piecesLoc[WHITE][1])
	//	{
	//		throw "found Incoherancy!!!";
	//	}
	//}
};